package 加密与安全;
/*
 分类：
 -可逆加密
 -对称加密：DES，3DES，AES，PBE
 -非对称加密：RSA，DSA，ECC
 -不可逆加密（单向加密）：MD5，SHA，HMAC


Hmac算法就是一种基于密钥的消息认证码算法，它的全称是Hash-based Message Authentication Code，是一种更安全的消息摘要算法。属于不可逆单向加密

Hmac算法总是和某种哈希算法配合起来用的。例如，我们使用MD5算法，对应的就是HmacMD5算法，它相当于“加盐”的MD5：
  HmacMD5 ≈ md5(secure_random_key, input)
因此，HmacMD5可以看作带有一个安全的key的MD5。使用HmacMD5而不是用MD5加salt，有如下好处：
 -HmacMD5使用的key长度是64字节，更安全；(MD5只有16个字节)
 -Hmac是标准算法，同样适用于SHA-1等其他哈希算法；
 -Hmac输出和原有的哈希算法长度一致。
可见，Hmac本质上就是把key混入摘要的算法。验证此哈希时，除了原始的输入数据，还要提供key。

不引入第三方库的情况下，JDK支持有限的摘要算法：

provider	算法
SunJCE	HmacMD5
SunJCE	HmacSHA1
SunJCE	HmacSHA224
SunJCE	HmacSHA256
SunJCE	HmacSHA384
SunJCE	HmacSHA512

BouncyCastle是一个提供了很多哈希算法和加密算法的第三方库。它提供了Java标准库没有的一些算法，如HmacSM3

MD5:message digest algorithm 5 信息摘要算法
SHA:secure hash algorithm 安全散列算法
HMAC:Hash-based Message Authentication Code 散列消息验证码

HMAC算法的一个典型应用是用在“挑战/响应”（Challenge/Response）身份认证中，认证流程如下：
(1) 先由客户端向服务器发出一个验证请求。
(2) 服务器接到此请求后生成一个随机数并通过网络传输给客户端（此为挑战）。 即salt
(3) 客户端将收到的随机数与自己的密钥(用户密码password)进行HMAC-SHA1运算并得到一个结果作为认证证据传给服务器（此为响应）。  salt+password
(4) 与此同时，服务器也使用该随机数与存储在服务器数据库中的该客户密钥进行HMAC-SHA1运算，如果服务器的运算结果与客户端传回的响应结果
    相同，则认为客户端是一个合法用户 。

HMAC算法引入了密钥，其安全性已经不完全依赖于所使用的HASH算法，安全性主要有以下几点保证：
（1）使用的密钥是双方事先约定的，第三方不可能知道。由上面介绍应用流程可以看出，作为非法截获信息的第三方，能够得到的信息只有作
    为“挑战”的随机数和作为“响应”的HMAC结果，无法根据这两个数据推算出密钥。由于不知道密钥，所以无法仿造出一致的响应。
（2）在HMAC算法的应用中，第三方不可能事先知道输出（如果知道，不用构造输入，直接将输出送给服务器即可）。
（3）HMAC算法与一般的加密重要的区别在于它具有“瞬时”性，即认证只在当时有效，而加密算法被破解后，以前的加密结果就可能被解密。


为了保证安全，我们不会自己指定key，而是通过Java标准库的KeyGenerator生成一个安全的随机的key。下面是使用HmacMD5的代码：
*/
import java.math.BigInteger;
import java.util.Arrays;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import static org.junit.jupiter.api.Assertions.*;
//import javax.crypto.*;

public class Hmac算法 {
	public static void main(String[] args) throws Exception {
      KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");//通过名称HmacMD5获取KeyGenerator实例；
      //KeyGenerator keyGen = KeyGenerator.getInstance("HmacSHA1");//通过名称HmacMD5获取KeyGenerator实例；
      SecretKey key = keyGen.generateKey();//通过KeyGenerator创建一个SecretKey实例；
      // 打印随机生成的key:
      byte[] skey = key.getEncoded();//key的二进制数组  即所谓:salt
      System.out.println("随机生成的key: "+new BigInteger(1, skey).toString(16));//64个字节
      System.out.println(Arrays.toString(skey));//64个字节
      //Mac mac = Mac.getInstance("HmacMD5");//通过名称HmacMD5获取Mac实例； Mac->Message Authentication Code
      Mac mac = Mac.getInstance("HmacSHA1");//通过名称HmacMD5获取Mac实例； Mac->Message Authentication Code
      mac.init(key);//用SecretKey初始化Mac实例；
      mac.update("user password".getBytes("UTF-8"));//对Mac实例反复调用update(byte[])输入数据；
      System.out.println("原始信息： "+Arrays.toString("user password".getBytes("UTF-8")));//[72, 101, 108, 108, 111, 87, 111, 114, 108, 100]
      
      byte[] result = mac.doFinal();//调用Mac实例的doFinal()获取最终的哈希值。
      System.out.println("最终的哈希值: "+new BigInteger(1, result).toString(16));
  }
}

/*
和MD5相比，使用HmacMD5的步骤是：
  1.通过名称HmacMD5获取KeyGenerator实例；
  2.通过KeyGenerator创建一个SecretKey实例；
  3.通过名称HmacMD5获取Mac实例；
  4.用SecretKey初始化Mac实例；
  5.对Mac实例反复调用update(byte[])输入数据；
  6.调用Mac实例的doFinal()获取最终的哈希值。
我们可以用Hmac算法取代原有的自定义的加盐算法，因此，存储用户名和口令的数据库结构如下：

username	 secret_key (64 bytes)	  password
bob	    a8c06e05f92e...5e16	     7e0387872a57c85ef6dddbaa12f376de
alice	    e6a343693985...f4be	     c1f929ac2552642b302e739bc0cdbaac
tim	    f27a973dfdc0...6003	     af57651c3a8a73303515804d4af43790
有了Hmac计算的哈希和SecretKey，我们想要验证怎么办？这时，SecretKey不能从KeyGenerator生成，而是从一个byte[]数组恢复：
*/
/*
//验证：
public class Hmac算法 {
   public static void main(String[] args) throws Exception {
       byte[] hkey = new byte[] { 106, 70, -110, 125, 39, -20, 52, 56, 85, 9, -19, -72, 52, -53, 52, -45, -6, 119, -63,
               30, 20, -83, -28, 77, 98, 109, -32, -76, 121, -106, 0, -74, -107, -114, -45, 104, -104, -8, 2, 121, 6,
               97, -18, -13, -63, -30, -125, -103, -80, -46, 113, -14, 68, 32, -46, 101, -116, -104, -81, -108, 122,
               89, -106, -109 };
       SecretKey key = new SecretKeySpec(hkey, "HmacMD5"); //恢复SecretKey
       Mac mac = Mac.getInstance("HmacMD5");
       mac.init(key);
       mac.update("HelloWorld".getBytes("UTF-8"));
       byte[] result = mac.doFinal();
       System.out.println(Arrays.toString(result));
       // [126, 59, 37, 63, 73, 90, 111, -96, -77, 15, 82, -74, 122, -55, -67, 54]
   }
}*/
/*
//已知字符串(key)、加密算法(algorithm)、原文(data)、哈希值，   -->验证原文哈希值：
public class Hmac算法 {
	public static void main(String[] args) throws Exception {
		//获得的原文的哈希值。
		String hmacSHA256Value = "5b50d80c7dc7ae8bb1b1433cc0b99ecd2ac8397a555c6f75cb8a619ae35a0c35";
		//获得的的原文(data)，即：需要验证的原文。
		String data = "baeldung";

		//已知哈希算法(algorithm)
		String hmacSHA256Algorithm = "HmacSHA256";
		//已知的口令(key)
		String key = "123456";
		//计算新的哈希值
		String result = hmacWithJava(hmacSHA256Algorithm, data,key);
		System.out.println(result);
		assertEquals(hmacSHA256Value, result);//与原来的哈希值比较，如果相同则未改动。
	}
	static String hmacWithJava(String algorithm, String data, String key) throws Exception {
		SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), algorithm);
//		SecretKey secretKeySpec = new SecretKeySpec(key.getBytes(), algorithm);
		Mac mac = Mac.getInstance(algorithm);
		mac.init(secretKeySpec);
		return bytesToHex(mac.doFinal(data.getBytes()));
	}
	static String bytesToHex(byte[] bytes) throws Exception{
		//String hex = new BigInteger(1, bytes).toString(16);
	   return (new BigInteger(1, bytes).toString(16));
	}
}
*/
